/*

Copyright (C) 2000 - 2008 Christian Kreibich <christian@whoop.org>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies of the Software and its documentation and acknowledgment shall be
given in the documentation and software packages that this Software was
used.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <dirent.h>
#include <glib.h>

// #include <nd.h>
#include <nd_dialog.h>
#include <nd_gui.h>
#include <nd_prefs.h>
#include <nd_protocol.h>
#include <nd_protocol_plugin.h>
#include <nd_types.h>
#include <callbacks.h>
// #include <interface.h>
// #include <support.h>

static GList *proto_plugins = NULL;  /* list<ND_ProtoPlugin*> */

static ND_ProtoPlugin *proto_plugin_new(char *filename);
static void       proto_plugin_scan(const char *dirname);
static gboolean   proto_plugin_hook_up(ND_ProtoPlugin *p);
static void       proto_plugin_load(void);
static void       proto_plugin_setup_gui(void);
static int        proto_plugin_cmp(ND_ProtoPlugin *p1, char *filename);

/* proto plugin menu items */
static ND_GtkMenu *pp_menu = NULL;
static void gui_test_activate(GtkWidget *w, gpointer data);

static void
gui_test_activate(GtkWidget *w, gpointer data)
{
	printf("works!\n");
}

static const char      *
proto_plugin_dummy_name(void)
{
	return _("Unnamed Plugin.");
}


static const char      *
proto_plugin_dummy_description(void)
{
	return _("No description available.");
}


static const char      *
proto_plugin_dummy_author(void)
{
	return _("No author(s) specified.");
}

static const char      *
proto_plugin_dummy_version(void)
{
	return _("No version specified.");
}


static int
proto_plugin_dummy_op(void)
{
	return 0;
}

static LND_Protocol *
proto_plugin_dummy_init(void)
{
	return NULL;
}


static ND_ProtoPlugin *
proto_plugin_new(char *filename)
{
	ND_ProtoPlugin *plugin;
	
	plugin = (ND_ProtoPlugin*) g_new0(ND_ProtoPlugin, 1);
	D_ASSERT_PTR(plugin);
	if (!plugin)
		return NULL;
	
	if (filename)
		plugin->filename = g_strdup(filename);
	
	plugin->name        = proto_plugin_dummy_name;
	plugin->description = proto_plugin_dummy_description;
	plugin->author      = proto_plugin_dummy_author;
	plugin->version     = proto_plugin_dummy_version;
	plugin->init        = proto_plugin_dummy_init;
	plugin->cleanup     = proto_plugin_dummy_op;
	
	return (plugin);
}


static void
proto_plugin_free(ND_ProtoPlugin *plugin)
{
	if (!plugin)
		return;
	
	lt_dlclose(plugin->lt);
	g_free(plugin->filename);
	g_free(plugin);
}


static int    
proto_plugin_cmp(ND_ProtoPlugin *p1, char *filename)
{
	if (!p1 || !filename)
		return (-1);
	
	return (strcmp(p1->filename, filename));
}


static void
proto_plugin_scan(const char *dir)
{
	DIR             *dirp;
	struct dirent   *de;
	char            *dot;
	struct stat      st;
	char             path[MAXPATHLEN];
	
	D(("Scanning '%s' for protocols\n", dir));
	
	if (! (dirp = opendir(dir)))
		return;
	
	for (de = readdir(dirp); de; de = readdir(dirp))
	{
		g_snprintf(path, MAXPATHLEN, "%s/%s", dir, de->d_name);
	
		if (stat(path, &st) != 0)
		{
			D(("Could not stat %s\n", path));
			continue;
		}
	
		if (! S_ISREG(st.st_mode))
		{
			D(("Skipping %s, not a regular file.\n", path));
			continue;
		}
	
		/* Chop off any file extension, if found. libltdl takes
		 * care of that.
		 */
		if ( (dot = strrchr(path, '.')))
			*dot = '\0';
	
		if (!g_list_find_custom(proto_plugins, path,
				(GCompareFunc)proto_plugin_cmp))
		{
			proto_plugins = g_list_append(proto_plugins,
					proto_plugin_new(path));
		}
	}
	
	closedir(dirp);
}


static gboolean
proto_plugin_hook_up(ND_ProtoPlugin *plugin)
{
	ND_Protocol *proto_gui;
	lt_ptr_t     sym;
	int          i;
	
	if ((sym = lt_dlsym(plugin->lt, "name")) != NULL)
		plugin->name = sym;
	if ((sym = lt_dlsym(plugin->lt, "description")) != NULL)
		plugin->description = sym;
	if ((sym = lt_dlsym(plugin->lt, "author")) != NULL)
		plugin->author = sym;
	if ((sym = lt_dlsym(plugin->lt, "version")) != NULL)
		plugin->version = sym;
	
	if ((sym = lt_dlsym(plugin->lt, "init")) != NULL)
		plugin->init = sym;
	if ((sym = lt_dlsym(plugin->lt, "cleanup")) != NULL)
		plugin->cleanup = sym;
	
	D(("Hooking up protocol %s\n", plugin->name()));
	
	/* Call the init routine the plugin provides: */
	if (! (plugin->proto = plugin->init()))
	{
		D(("No plugin available for a GUI\n"));
		return FALSE;
	}
	
	proto_gui = nd_proto_get(plugin->proto);
	
	/* If the plugin provides a header fields table,
	 * calculate the header size
	 */
	if (proto_gui->fields)
	{
		for (i = 0, proto_gui->header_size = 0; proto_gui->fields[i].label; i++)
		proto_gui->header_size += proto_gui->fields[i].bits;
	}
	
	/* Hook in the plugin: */
	proto_gui->plugin = plugin;
	
	fprintf(stderr, "proto_plugin_hook_up: returning true\n");
	return TRUE;
}


static gint
proto_plugin_strcmp(gconstpointer a, gconstpointer b)
{
	ND_ProtoPlugin *p1 = (ND_ProtoPlugin *) a;
	ND_ProtoPlugin *p2 = (ND_ProtoPlugin *) b;
	
	return strcmp(p1->name(), p2->name());
}

static void
proto_plugin_load(void)
{
	GList            *l, *l2;
	ND_ProtoPlugin   *plugin;
	char              s[MAXPATHLEN];
	
	for (l = g_list_first(proto_plugins); l; l = g_list_next(l))
	{
		plugin = (ND_ProtoPlugin *) l->data;
	
		if (! (plugin->lt = lt_dlopenext(plugin->filename)) )
		{
			if (! nd_runtime_options.show_plugins)
			{
				fprintf(stderr, "Unable to load protocol plugin '%s' lt_dlopenext failed with '%s'.\n",
						(plugin->name != proto_plugin_dummy_name ?
								plugin->name() : plugin->filename), lt_dlerror());
				g_snprintf(s, MAXPATHLEN,
						   _("Unable to load protocol plugin '%s'.\n"
								   "If this plugin depends on other plugins,\n"
								   "make sure you have those installed."),
		   (plugin->name != proto_plugin_dummy_name ?
				   plugin->name() : plugin->filename));
				nd_dialog_error(nd_toplevel_window, _("Plugin error"), s, FALSE);
			}
			
			l->data = NULL;
			proto_plugin_free(plugin);
			break;
		}
		
		if (! (proto_plugin_hook_up(plugin)) )
		{
			if (! nd_runtime_options.show_plugins)
			{
				fprintf(stderr, "Unable to load protocol plugin '%s' proto_plugin_hook_up failed.\n",
						(plugin->name != proto_plugin_dummy_name ?
								plugin->name() : plugin->filename));
				g_snprintf(s, MAXPATHLEN,
						_("Unable to load protocol plugin '%s'.\n"
						"If this plugin depends on other plugins,\n"
						"make sure you have those installed."),
						(plugin->name != proto_plugin_dummy_name ?
								plugin->name() : plugin->filename));
				nd_dialog_error(nd_toplevel_window, _("Plugin error"), s, FALSE);
			}
			
			l->data = NULL;
			proto_plugin_free(plugin);
		}
	}
	
	/* We may have deleted some plugins above that failed to initialize.
	 * Clear out those list items in a separate iteration.
	 */
	for (l = g_list_first(proto_plugins); l; )
	{
		if (l->data == NULL)
		{
			l2 = l;
			l = g_list_next(l);
			proto_plugins = g_list_remove_link(proto_plugins, l2);
			g_list_free_1(l2);
		}
		else
		{
			l = g_list_next(l);
		}
	}
	
	/* Now sort the list of plugins alphanumerically,
	 * so that we automatically get sorted menus etc.
	 */
	proto_plugins = g_list_sort(proto_plugins, proto_plugin_strcmp);
}


static void
proto_plugin_setup_gui(void)
{	
	// *****************************************
	// * Test adding menus with GtkUIManager
	// * START
	// *****************************************
	
// 	GtkAction           *action = NULL;
// 	
// 	// Add a new action group
// 	pp_menu->action_group = gtk_action_group_new("proto_plugin_actions");
// 	gtk_ui_manager_insert_action_group(nd_ui_manager, pp_menu->action_group, 0);
// 	
// 	// Create the actions
// 	action = gtk_action_new("test_action1", "Test!", NULL, NULL);
// 	pp_menu->item_actions = g_slist_append(pp_menu->item_actions, action);
// 	action = gtk_action_new("test_action2", "Test!", NULL, NULL);
// 	pp_menu->item_actions = g_slist_append(pp_menu->item_actions, action);
// 	
// 	// Add the actions to the action group
// 	gtk_action_group_add_action_with_accel(pp_menu->action_group,
// 			g_slist_nth_data(pp_menu->item_actions, 0), "");
// 	gtk_action_group_add_action_with_accel(pp_menu->action_group,
// 			g_slist_nth_data(pp_menu->item_actions, 1), "");
// 	
// 	// Add a callback for the action
// 	g_signal_connect(G_OBJECT(g_slist_nth_data(pp_menu->item_actions, 0)), "activate",
// 			G_CALLBACK(gui_test_activate), NULL);
// 	g_signal_connect(G_OBJECT(g_slist_nth_data(pp_menu->item_actions, 1)), "activate",
// 			G_CALLBACK(gui_test_activate), NULL);
// 	
// 	// Add a test item to the plugin menu that utilizes the action
// 	pp_menu->item_merge_ids = malloc(sizeof(guint) * (pp_menu->item_merge_id_count+1));
// 	pp_menu->item_merge_ids[pp_menu->item_merge_id_count] = gtk_ui_manager_new_merge_id(nd_ui_manager);
// 	fprintf(stderr, "adding merge_id: %u; nd_ui_manager: %p\n", pp_menu->item_merge_ids[pp_menu->item_merge_id_count], nd_ui_manager);
// 	gtk_ui_manager_add_ui(nd_ui_manager, pp_menu->item_merge_ids[pp_menu->item_merge_id_count++], "/ui/menubar1/help1/about_plugins",
// 			"test1", "test_action1", GTK_UI_MANAGER_MENUITEM, FALSE);
// 	pp_menu->item_merge_ids = realloc(pp_menu->item_merge_ids, sizeof(guint) * (pp_menu->item_merge_id_count+1));
// 	pp_menu->item_merge_ids[pp_menu->item_merge_id_count] = gtk_ui_manager_new_merge_id(nd_ui_manager);
// 	fprintf(stderr, "adding merge_id: %u; nd_ui_manager: %p\n", pp_menu->item_merge_ids[pp_menu->item_merge_id_count], nd_ui_manager);
// 	gtk_ui_manager_add_ui(nd_ui_manager, pp_menu->item_merge_ids[pp_menu->item_merge_id_count++], "/ui/menubar1/help1/about_plugins",
// 			"test2", "test_action2", GTK_UI_MANAGER_MENUITEM, FALSE);
	
	// *****************************************
	// * END
	// * Test adding menus with GtkUIManager
	// *****************************************


	// GtkWidget        *about_protocols, *menu, *item;
	GList            *l;
	ND_ProtoPlugin   *plugin;
	
	// /* Hook in protocol menus: */
	
	// ND_GTK_GET(about_protocols, nd_toplevel_window, "about_protocols");
	
	// menu = gtk_menu_new();
	// gtk_widget_ref (menu);
	// gtk_object_set_data_full (GTK_OBJECT (nd_toplevel_window), "about_protocols_menu", menu,
			// (GtkDestroyNotify) gtk_widget_unref);
	// gtk_menu_item_set_submenu (GTK_MENU_ITEM (about_protocols), menu);
	
	for (l = g_list_first(proto_plugins); l; l = g_list_next(l))
	{
		plugin = (ND_ProtoPlugin*)l->data;
	
// 		item = gtk_menu_item_new_with_label(plugin->name());
// 		gtk_widget_ref(item);
// 		gtk_object_set_data_full (GTK_OBJECT (menu), plugin->name(), item,
// 				(GtkDestroyNotify) gtk_widget_unref);
// 		gtk_widget_show(item);
// 		gtk_object_set_data(GTK_OBJECT(item), "plugin", plugin);
// 		gtk_container_add(GTK_CONTAINER(menu), item);
// 		gtk_signal_connect (GTK_OBJECT (item), "activate",
// 				GTK_SIGNAL_FUNC (on_about_proto_activate),
// 				NULL);
		
		nd_gui_proto_menu_register(plugin->proto);
	}
}


static void
proto_plugin_shutdown_gui(void)
{
// 	GSList			*l;
// 	unsigned		 i;
// 	
// 	// Remove the test items
// 	fprintf(stderr, "count: %u\n", pp_menu->item_merge_id_count);
// 	for (i = 0; i < pp_menu->item_merge_id_count; ++i) {
// 		fprintf(stderr, "removing merge_id: %u; nd_ui_manager: %p\n", pp_menu->item_merge_ids[i], nd_ui_manager);
// 		gtk_ui_manager_remove_ui(nd_ui_manager, pp_menu->item_merge_ids[i]);
// 	}
// 	
// 	// Remove the actions from the action group
// 	for (l = pp_menu->item_actions; l; l = g_slist_next(l)) {
// 		gtk_action_group_remove_action(pp_menu->action_group, (GtkAction *)l->data);
// 	}
// 	
// 	// GTK should free the actions because they are no longer
// 	// referenced by other GTK objects
// 	
// 	// Free the action pointer array
// 	g_slist_free(pp_menu->item_actions);
// 	
// 	// Free the merge id array
// 	free(pp_menu->item_merge_ids);
// 	
// 	// Remove the action group
// 	gtk_ui_manager_remove_action_group(nd_ui_manager, pp_menu->action_group);
}


void
nd_proto_plugin_shutdown(void)
{
	proto_plugin_shutdown_gui();
}


void 
nd_proto_plugin_init(void)
{
	D_ENTER;
	
	if (lt_dlinit() != 0)
	{
		fprintf(stderr, "Could not initialize libltdl -- exiting.\n");
		exit(-1);
	}
	
// 	pp_menu = calloc(1, sizeof(pp_menu_t));
	
	proto_plugin_scan(nd_prefs_get_proto_dir_user());
	proto_plugin_scan(nd_prefs_get_proto_dir_global());
	proto_plugin_load();
 	proto_plugin_setup_gui();
	
	D_RETURN;
}


const char   *
nd_proto_plugin_get_description(ND_ProtoPlugin *plugin)
{
	if (!plugin)
		return (NULL);
	
	return (plugin->description());
}


const char   *
nd_proto_plugin_get_author(ND_ProtoPlugin *plugin)
{
	if (!plugin)
		return (NULL);
	
	return (plugin->author());
}


const char   *
nd_proto_plugin_get_name(ND_ProtoPlugin *plugin)
{
	if (!plugin)
		return (NULL);
	
	return (plugin->name());
}


const char   *
nd_proto_plugin_get_version(ND_ProtoPlugin *plugin)
{
	if (!plugin)
		return (NULL);
	
	return (plugin->version());
}


// void
// nd_proto_plugin_show_about(ND_ProtoPlugin *plugin)
// {
// 	char       s[MAXPATHLEN];
// 	GtkWidget *w;
// 	GtkLabel  *label;
// 	
// 	D_ASSERT_PTR(plugin);
// 	if (!plugin)
// 		return;
// 	
// 	w = create_protocol_about_dialog();
// 	
// 	g_snprintf(s, MAXPATHLEN, "%s, %s.", plugin->name(), plugin->version());
// 	ND_GTK_GET_TYPE(GTK_LABEL, label, pg_objs, "proto_name_label");
// 	gtk_label_set_text(label, s);
// 	
// 	g_snprintf(s, MAXPATHLEN, _("Author: %s"), plugin->author());
// 	ND_GTK_GET_TYPE(GTK_LABEL, label, pg_objs, "proto_author_label");
// 	gtk_label_set_text(label, s);
// 	
// 	ND_GTK_GET_TYPE(GTK_LABEL, label, pg_objs, "proto_description_label");
// 	gtk_label_set_text(label, plugin->description());
// 	
// 	gtk_widget_show(w);
// }


void
nd_proto_plugin_foreach(ND_ProtoPluginFunc callback,
			void *user_data)
{
	GList *l;
	
	if (!callback)
		return;
	
	for (l = g_list_first(proto_plugins); l; l = g_list_next(l))
		callback((ND_ProtoPlugin *)l->data, user_data);
}

